<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>视频音频替换工具</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/ffmpeg/0.11.6/ffmpeg.min.js"></script>
    <style>
      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
        font-family: 'SF Pro Display', 'PingFang SC', 'Helvetica Neue', Arial,
          sans-serif;
      }

      :root {
        --primary-bg: #e6e7ee;
        --secondary-bg: #ffffff;
        --shadow-light: #ffffff;
        --shadow-dark: #d1d9e6;
        --accent-color: #667eea;
        --accent-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        --success-color: #4ecdc4;
        --danger-color: #ff6b6b;
        --warning-color: #feca57;
        --text-primary: #2d3748;
        --text-secondary: #718096;
        --glass-bg: rgba(255, 255, 255, 0.25);
        --glass-border: rgba(255, 255, 255, 0.18);
      }

      body {
        background: var(--primary-bg);
        color: var(--text-primary);
        min-height: 100vh;
        padding: 20px;
        display: flex;
        flex-direction: column;
        align-items: center;
        transition: all 0.3s ease;
        animation: pageLoad 0.8s ease;
      }

      @keyframes pageLoad {
        from {
          opacity: 0;
          transform: translateY(20px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }

      .container {
        width: 100%;
        max-width: 1200px;
        background: var(--primary-bg);
        border-radius: 30px;
        padding: 40px;
        box-shadow: 20px 20px 60px var(--shadow-dark),
          -20px -20px 60px var(--shadow-light);
        margin-top: 20px;
        transition: all 0.3s ease;
        animation: containerFloat 1s ease 0.2s both;
      }

      @keyframes containerFloat {
        from {
          opacity: 0;
          transform: translateY(30px) scale(0.95);
        }
        to {
          opacity: 1;
          transform: translateY(0) scale(1);
        }
      }

      .container:hover {
        box-shadow: 25px 25px 70px var(--shadow-dark),
          -25px -25px 70px var(--shadow-light);
      }

      header {
        text-align: center;
        margin-bottom: 40px;
        padding: 30px;
        background: var(--primary-bg);
        border-radius: 25px;
        box-shadow: 10px 10px 20px var(--shadow-dark),
          -10px -10px 20px var(--shadow-light);
        animation: headerSlide 0.8s ease 0.4s both;
      }

      @keyframes headerSlide {
        from {
          opacity: 0;
          transform: translateY(-30px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }

      h1 {
        font-size: 3.2rem;
        margin-bottom: 15px;
        background: var(--accent-gradient);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
        background-clip: text;
        font-weight: 700;
        animation: titleGlow 3s ease-in-out infinite alternate;
      }

      @keyframes titleGlow {
        from {
          filter: brightness(1);
        }
        to {
          filter: brightness(1.2);
        }
      }

      .subtitle {
        font-size: 1.3rem;
        color: var(--text-secondary);
        margin-bottom: 30px;
        font-weight: 500;
      }

      .panels {
        display: flex;
        gap: 30px;
        margin-bottom: 40px;
        animation: panelsSlide 0.8s ease 0.6s both;
      }

      @keyframes panelsSlide {
        from {
          opacity: 0;
          transform: translateX(-20px);
        }
        to {
          opacity: 1;
          transform: translateX(0);
        }
      }

      .panel {
        flex: 1;
        background: var(--primary-bg);
        border-radius: 25px;
        padding: 30px;
        box-shadow: 12px 12px 24px var(--shadow-dark),
          -12px -12px 24px var(--shadow-light);
        transition: all 0.3s ease;
        position: relative;
        overflow: hidden;
      }

      .panel::before {
        content: '';
        position: absolute;
        top: 0;
        left: -100%;
        width: 100%;
        height: 100%;
        background: linear-gradient(
          90deg,
          transparent,
          rgba(255, 255, 255, 0.1),
          transparent
        );
        transition: left 0.8s;
      }

      .panel:hover::before {
        left: 100%;
      }

      .panel:hover {
        transform: translateY(-5px);
        box-shadow: 16px 16px 32px var(--shadow-dark),
          -16px -16px 32px var(--shadow-light);
      }

      .panel-title {
        font-size: 1.6rem;
        margin-bottom: 25px;
        color: var(--accent-color);
        display: flex;
        align-items: center;
        font-weight: 600;
      }

      .panel-title::before {
        content: attr(data-icon);
        margin-right: 12px;
        font-size: 1.8rem;
        animation: iconBounce 2s ease-in-out infinite;
      }

      @keyframes iconBounce {
        0%,
        100% {
          transform: scale(1);
        }
        50% {
          transform: scale(1.1);
        }
      }

      .file-upload {
        border: 3px dashed var(--accent-color);
        border-radius: 25px;
        padding: 50px 40px;
        text-align: center;
        transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
        margin-bottom: 25px;
        cursor: pointer;
        background: var(--primary-bg);
        box-shadow: inset 8px 8px 16px var(--shadow-dark),
          inset -8px -8px 16px var(--shadow-light);
        position: relative;
        overflow: hidden;
      }

      .file-upload:hover {
        border-color: var(--success-color);
        transform: translateY(-5px) scale(1.02);
        box-shadow: inset 10px 10px 20px var(--shadow-dark),
          inset -10px -10px 20px var(--shadow-light),
          0 15px 40px rgba(102, 126, 234, 0.25), 0 5px 15px rgba(0, 0, 0, 0.1);
      }

      .file-upload.dragover {
        border-color: var(--success-color);
        background: rgba(78, 205, 196, 0.05);
        transform: scale(1.05);
        box-shadow: inset 12px 12px 24px var(--shadow-dark),
          inset -12px -12px 24px var(--shadow-light),
          0 20px 50px rgba(78, 205, 196, 0.3);
        animation: dragPulse 1s ease-in-out infinite;
      }

      @keyframes dragPulse {
        0%,
        100% {
          border-color: var(--success-color);
        }
        50% {
          border-color: var(--accent-color);
        }
      }

      .file-upload-content {
        position: relative;
        z-index: 1;
      }

      .file-upload-icon {
        font-size: 3.5rem;
        margin-bottom: 20px;
        display: block;
        animation: iconFloat 3s ease-in-out infinite;
        filter: drop-shadow(0 4px 8px rgba(102, 126, 234, 0.2));
      }

      @keyframes iconFloat {
        0%,
        100% {
          transform: translateY(0);
        }
        50% {
          transform: translateY(-10px);
        }
      }

      .file-upload-text {
        font-size: 1.4rem;
        font-weight: 600;
        color: var(--text-primary);
        margin-bottom: 10px;
        background: var(--accent-gradient);
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
        background-clip: text;
      }

      .file-upload-subtext {
        font-size: 1.1rem;
        color: var(--text-secondary);
        margin-bottom: 20px;
        font-weight: 500;
      }

      .file-upload-formats {
        font-size: 0.9rem;
        color: var(--accent-color);
        font-weight: 500;
        padding: 8px 16px;
        background: rgba(102, 126, 234, 0.1);
        border-radius: 20px;
        display: inline-block;
        margin-bottom: 20px;
      }

      .file-upload input[type='file'] {
        position: absolute;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        opacity: 0;
        cursor: pointer;
        z-index: 2;
      }

      .video-preview {
        width: 100%;
        background: var(--primary-bg);
        border-radius: 20px;
        overflow: hidden;
        margin-top: 20px;
        box-shadow: inset 10px 10px 20px var(--shadow-dark),
          inset -10px -10px 20px var(--shadow-light);
        transition: all 0.3s ease;
      }

      .video-preview:hover {
        box-shadow: inset 12px 12px 24px var(--shadow-dark),
          inset -12px -12px 24px var(--shadow-light);
      }

      video {
        width: 100%;
        max-height: 300px;
        display: block;
        border-radius: 15px;
      }

      audio {
        width: 100%;
        margin-top: 10px;
      }

      button {
        background: var(--primary-bg);
        color: var(--text-primary);
        border: none;
        padding: 14px 28px;
        border-radius: 15px;
        cursor: pointer;
        font-weight: 600;
        transition: all 0.3s ease;
        font-size: 1rem;
        flex-shrink: 0;
        box-shadow: 8px 8px 16px var(--shadow-dark),
          -8px -8px 16px var(--shadow-light);
        position: relative;
        overflow: hidden;
      }

      button::before {
        content: '';
        position: absolute;
        top: 0;
        left: -100%;
        width: 100%;
        height: 100%;
        background: linear-gradient(
          90deg,
          transparent,
          rgba(255, 255, 255, 0.2),
          transparent
        );
        transition: left 0.5s;
      }

      button:hover::before {
        left: 100%;
      }

      button:hover {
        transform: translateY(-3px);
        box-shadow: 12px 12px 24px var(--shadow-dark),
          -12px -12px 24px var(--shadow-light);
      }

      button:active {
        transform: translateY(0);
        box-shadow: inset 6px 6px 12px var(--shadow-dark),
          inset -6px -6px 12px var(--shadow-light);
      }

      button:disabled {
        opacity: 0.6;
        cursor: not-allowed;
        transform: none;
        box-shadow: 4px 4px 8px var(--shadow-dark),
          -4px -4px 8px var(--shadow-light);
      }

      .btn-primary {
        background: var(--accent-gradient);
        color: white;
        padding: 16px 32px;
        font-size: 1.1rem;
        box-shadow: 8px 8px 16px var(--shadow-dark),
          -8px -8px 16px var(--shadow-light),
          inset 0 1px 0 rgba(255, 255, 255, 0.2);
        margin: 20px auto;
        display: block;
      }

      .btn-primary:hover {
        box-shadow: 12px 12px 24px var(--shadow-dark),
          -12px -12px 24px var(--shadow-light),
          inset 0 1px 0 rgba(255, 255, 255, 0.2),
          0 0 30px rgba(102, 126, 234, 0.3);
      }

      .progress-container {
        margin: 30px 0;
        animation: progressSlide 0.8s ease 0.8s both;
        display: none;
      }

      @keyframes progressSlide {
        from {
          opacity: 0;
          transform: translateY(20px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }

      .progress-bar {
        height: 16px;
        background: var(--primary-bg);
        border-radius: 20px;
        overflow: hidden;
        margin-bottom: 15px;
        box-shadow: inset 6px 6px 12px var(--shadow-dark),
          inset -6px -6px 12px var(--shadow-light);
        position: relative;
      }

      .progress-fill {
        height: 100%;
        background: var(--accent-gradient);
        width: 0%;
        transition: width 0.3s ease;
        border-radius: 20px;
        box-shadow: 0 2px 10px rgba(102, 126, 234, 0.3);
        position: relative;
        overflow: hidden;
      }

      .progress-fill::after {
        content: '';
        position: absolute;
        top: 0;
        left: -100%;
        width: 100%;
        height: 100%;
        background: linear-gradient(
          90deg,
          transparent,
          rgba(255, 255, 255, 0.3),
          transparent
        );
        animation: progressShine 2s infinite;
      }

      @keyframes progressShine {
        0% {
          left: -100%;
        }
        100% {
          left: 100%;
        }
      }

      .progress-text {
        text-align: center;
        font-size: 1rem;
        color: var(--text-secondary);
        font-weight: 500;
      }

      .status-message {
        min-height: 35px;
        margin: 20px 0;
        color: var(--accent-color);
        font-weight: 600;
        font-size: 1.1rem;
        transition: all 0.3s ease;
        text-align: center;
      }

      .status-message.processing {
        animation: statusPulse 1.5s ease-in-out infinite;
      }

      @keyframes statusPulse {
        0%,
        100% {
          opacity: 1;
        }
        50% {
          opacity: 0.6;
        }
      }

      .error-message {
        color: var(--danger-color);
        margin: 20px 0;
        min-height: 35px;
        font-weight: 600;
        font-size: 1.1rem;
        animation: errorShake 0.5s ease;
        text-align: center;
      }

      @keyframes errorShake {
        0%,
        100% {
          transform: translateX(0);
        }
        25% {
          transform: translateX(-5px);
        }
        75% {
          transform: translateX(5px);
        }
      }

      .result-section {
        display: none;
        text-align: center;
        margin-top: 30px;
        padding: 25px;
        background: var(--primary-bg);
        border-radius: 25px;
        box-shadow: 10px 10px 20px var(--shadow-dark),
          -10px -10px 20px var(--shadow-light);
      }

      .result-video {
        width: 100%;
        max-width: 600px;
        margin: 20px auto;
        border-radius: 15px;
        box-shadow: 8px 8px 16px var(--shadow-dark),
          -8px -8px 16px var(--shadow-light);
      }

      .download-btn {
        background: linear-gradient(
          135deg,
          var(--success-color) 0%,
          #44a08d 100%
        );
        color: white;
        padding: 16px 32px;
        font-size: 1.1rem;
        margin: 20px 10px;
        box-shadow: 8px 8px 16px var(--shadow-dark),
          -8px -8px 16px var(--shadow-light),
          inset 0 1px 0 rgba(255, 255, 255, 0.2);
      }

      .file-info {
        margin-top: 15px;
        padding: 15px;
        background: rgba(102, 126, 234, 0.1);
        border-radius: 15px;
        font-size: 0.9rem;
        color: var(--text-secondary);
      }

      .instructions {
        background: var(--primary-bg);
        border-radius: 25px;
        padding: 30px;
        margin-top: 40px;
        box-shadow: 10px 10px 20px var(--shadow-dark),
          -10px -10px 20px var(--shadow-light);
        animation: instructionsSlide 0.8s ease 1.4s both;
      }

      @keyframes instructionsSlide {
        from {
          opacity: 0;
          transform: translateY(30px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }

      .instructions h3 {
        color: var(--accent-color);
        margin-bottom: 20px;
        font-size: 1.4rem;
        font-weight: 600;
      }

      .instructions ol {
        padding-left: 25px;
        margin-bottom: 20px;
      }

      .instructions li {
        margin-bottom: 12px;
        line-height: 1.7;
        color: var(--text-secondary);
        font-weight: 500;
      }

      .instructions p {
        color: var(--text-secondary);
        font-style: italic;
        font-weight: 500;
        padding: 15px;
        background: rgba(102, 126, 234, 0.1);
        border-radius: 15px;
        border-left: 4px solid var(--accent-color);
      }

      footer {
        margin-top: 40px;
        text-align: center;
        color: var(--text-secondary);
        font-size: 0.95rem;
        padding: 25px;
        background: var(--primary-bg);
        border-radius: 20px;
        box-shadow: 6px 6px 12px var(--shadow-dark),
          -6px -6px 12px var(--shadow-light);
        animation: footerFade 0.8s ease 1.6s both;
      }

      @keyframes footerFade {
        from {
          opacity: 0;
        }
        to {
          opacity: 1;
        }
      }

      /* 模式选择样式 */
      .mode-selection {
        background: var(--primary-bg);
        border-radius: 25px;
        padding: 30px;
        margin: 30px 0;
        box-shadow: 12px 12px 24px var(--shadow-dark),
          -12px -12px 24px var(--shadow-light);
        animation: modeSlide 0.8s ease 1s both;
      }

      @keyframes modeSlide {
        from {
          opacity: 0;
          transform: translateY(20px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }

      .mode-title {
        font-size: 1.6rem;
        color: var(--accent-color);
        margin-bottom: 25px;
        text-align: center;
        font-weight: 600;
      }

      .mode-options {
        display: flex;
        gap: 20px;
        margin-bottom: 30px;
      }

      .mode-option {
        flex: 1;
        position: relative;
      }

      .mode-option input[type='radio'] {
        position: absolute;
        opacity: 0;
        width: 0;
        height: 0;
      }

      .mode-label {
        display: flex;
        align-items: center;
        padding: 20px;
        background: var(--primary-bg);
        border-radius: 20px;
        cursor: pointer;
        transition: all 0.3s ease;
        box-shadow: 8px 8px 16px var(--shadow-dark),
          -8px -8px 16px var(--shadow-light);
        border: 2px solid transparent;
      }

      .mode-label:hover {
        transform: translateY(-3px);
        box-shadow: 12px 12px 24px var(--shadow-dark),
          -12px -12px 24px var(--shadow-light);
      }

      .mode-option input[type='radio']:checked + .mode-label {
        border-color: var(--accent-color);
        box-shadow: inset 4px 4px 8px var(--shadow-dark),
          inset -4px -4px 8px var(--shadow-light),
          0 0 20px rgba(102, 126, 234, 0.3);
      }

      .mode-icon {
        font-size: 2.5rem;
        margin-right: 15px;
        animation: iconPulse 2s ease-in-out infinite;
      }

      @keyframes iconPulse {
        0%,
        100% {
          transform: scale(1);
        }
        50% {
          transform: scale(1.1);
        }
      }

      .mode-content {
        flex: 1;
      }

      .mode-name {
        font-size: 1.2rem;
        font-weight: 600;
        color: var(--text-primary);
        margin-bottom: 5px;
      }

      .mode-desc {
        font-size: 0.9rem;
        color: var(--text-secondary);
        line-height: 1.4;
      }

      /* 音频截取设置样式 */
      .audio-trim-settings {
        background: var(--primary-bg);
        border-radius: 20px;
        padding: 25px;
        box-shadow: inset 6px 6px 12px var(--shadow-dark),
          inset -6px -6px 12px var(--shadow-light);
        transition: all 0.3s ease;
      }

      .audio-trim-settings.hidden {
        display: none;
      }

      .settings-title {
        font-size: 1.3rem;
        color: var(--accent-color);
        margin-bottom: 20px;
        font-weight: 600;
      }

      .trim-controls {
        display: flex;
        gap: 20px;
        margin-bottom: 15px;
      }

      .control-group {
        flex: 1;
      }

      .control-group label {
        display: block;
        font-size: 1rem;
        color: var(--text-primary);
        margin-bottom: 8px;
        font-weight: 500;
      }

      .control-group input[type='number'] {
        width: 100%;
        padding: 12px 16px;
        border: none;
        border-radius: 15px;
        background: var(--primary-bg);
        box-shadow: inset 6px 6px 12px var(--shadow-dark),
          inset -6px -6px 12px var(--shadow-light);
        font-size: 1rem;
        color: var(--text-primary);
        transition: all 0.3s ease;
      }

      .control-group input[type='number']:focus {
        outline: none;
        box-shadow: inset 8px 8px 16px var(--shadow-dark),
          inset -8px -8px 16px var(--shadow-light),
          0 0 0 2px var(--accent-color);
      }

      .trim-note {
        font-size: 0.9rem;
        color: var(--text-secondary);
        font-style: italic;
        padding: 10px 15px;
        background: rgba(102, 126, 234, 0.1);
        border-radius: 10px;
        border-left: 3px solid var(--accent-color);
      }

      @media (max-width: 768px) {
        .panels {
          flex-direction: column;
        }

        h1 {
          font-size: 2.5rem;
        }

        .container {
          padding: 20px;
        }

        .mode-options {
          flex-direction: column;
        }

        .trim-controls {
          flex-direction: column;
        }
      }
    </style>
  </head>
  <body>
    <div class="container">
      <header>
        <h1>🎬 视频音频替换工具</h1>
        <p class="subtitle">使用 FFmpeg.wasm 在浏览器中替换视频的音频轨道</p>
      </header>

      <div class="panels">
        <!-- 视频上传面板 -->
        <div class="panel">
          <h3 class="panel-title" data-icon="🎥">上传视频文件</h3>
          <div class="file-upload" id="videoUpload">
            <div class="file-upload-content">
              <span class="file-upload-icon">🎬</span>
              <div class="file-upload-text">点击或拖拽上传视频</div>
              <div class="file-upload-subtext">支持多种视频格式</div>
              <div class="file-upload-formats">MP4, AVI, MOV, MKV, WebM</div>
            </div>
            <input type="file" id="videoInput" accept="video/*" />
          </div>
          <div class="video-preview" id="videoPreview" style="display: none">
            <video id="videoElement" controls></video>
            <div class="file-info" id="videoInfo"></div>
          </div>
        </div>

        <!-- 音频上传面板 -->
        <div class="panel">
          <h3 class="panel-title" data-icon="🎵">上传音频文件</h3>
          <div class="file-upload" id="audioUpload">
            <div class="file-upload-content">
              <span class="file-upload-icon">🎵</span>
              <div class="file-upload-text">点击或拖拽上传音频</div>
              <div class="file-upload-subtext">支持多种音频格式</div>
              <div class="file-upload-formats">MP3, WAV, AAC, OGG, FLAC</div>
            </div>
            <input type="file" id="audioInput" accept="audio/*" />
          </div>
          <div class="video-preview" id="audioPreview" style="display: none">
            <audio id="audioElement" controls></audio>
            <div class="file-info" id="audioInfo"></div>
          </div>
        </div>

        <!-- 字幕上传面板 -->
        <div class="panel">
          <h3 class="panel-title" data-icon="💬">上传字幕文件 (可选)</h3>
          <div class="file-upload" id="subtitleUpload">
            <div class="file-upload-content">
              <span class="file-upload-icon">📝</span>
              <div class="file-upload-text">点击或拖拽上传字幕</div>
              <div class="file-upload-subtext">支持 LRC、SRT、ASS 格式</div>
              <div class="file-upload-formats">字幕将融合到视频中</div>
            </div>
            <input type="file" id="subtitleInput" accept=".lrc,.srt,.ass" />
          </div>
          <div class="video-preview" id="subtitlePreview" style="display: none">
            <div class="file-info" id="subtitleInfo"></div>
          </div>

          <!-- 字幕样式设置 -->
          <div
            class="audio-trim-settings"
            id="subtitleSettings"
            style="margin-top: 20px; display: none"
          >
            <h4 class="settings-title">🎨 字幕样式设置</h4>
            <div class="trim-controls">
              <div class="control-group">
                <label for="subtitleStyle">字幕样式:</label>
                <select id="subtitleStyle">
                  <option value="default" selected>默认样式</option>
                  <option value="bold">加粗样式</option>
                  <option value="shadow">阴影样式</option>
                </select>
              </div>
              <div class="control-group">
                <label for="fontSizeMode">字体大小:</label>
                <select id="fontSizeMode">
                  <option value="auto" selected>自动适配分辨率</option>
                  <option value="custom">自定义大小</option>
                </select>
                <div
                  style="
                    margin-top: 6px;
                    font-size: 0.75rem;
                    color: var(--warning-color);
                    text-align: center;
                  "
                >
                  ⚠️ 仅适用于SRT字幕
                </div>
              </div>
            </div>

            <!-- 自定义字体大小控件 -->
            <div
              class="trim-controls"
              id="customFontSizeGroup"
              style="display: none; margin-top: 15px"
            >
              <div class="control-group">
                <label for="customFontSize">字体大小 (px):</label>
                <input
                  type="range"
                  id="customFontSize"
                  min="16"
                  max="80"
                  value="36"
                  step="2"
                />
                <div
                  style="
                    text-align: center;
                    margin-top: 5px;
                    font-size: 0.9rem;
                    color: var(--text-secondary);
                  "
                >
                  <span id="fontSizeValue">36</span> px
                </div>
                <div
                  style="
                    text-align: center;
                    margin-top: 8px;
                    font-size: 0.8rem;
                    color: var(--warning-color);
                    background: rgba(254, 202, 87, 0.1);
                    padding: 6px 12px;
                    border-radius: 12px;
                    border-left: 3px solid var(--warning-color);
                  "
                >
                  ✅ 适用于所有字幕格式，ASS字幕会自动规范化
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <!-- 模式选择区域 -->
      <div class="mode-selection" id="modeSelection">
        <h3 class="mode-title">🎯 选择处理模式</h3>
        <div class="mode-options">
          <div class="mode-option">
            <input
              type="radio"
              id="videoMode"
              name="mode"
              value="video"
              checked
            />
            <label for="videoMode" class="mode-label">
              <div class="mode-icon">🎥</div>
              <div class="mode-content">
                <div class="mode-name">以视频时长为主</div>
                <div class="mode-desc">音频将被截取或循环以匹配视频时长</div>
              </div>
            </label>
          </div>
          <div class="mode-option">
            <input type="radio" id="audioMode" name="mode" value="audio" />
            <label for="audioMode" class="mode-label">
              <div class="mode-icon">🎵</div>
              <div class="mode-content">
                <div class="mode-name">以音频时长为主</div>
                <div class="mode-desc">视频将循环播放直到音频结束</div>
              </div>
            </label>
          </div>
        </div>

        <!-- 音频截取设置 -->
        <div class="audio-trim-settings" id="audioTrimSettings">
          <h4 class="settings-title">🎵 音频截取设置</h4>
          <div class="trim-controls">
            <div class="control-group">
              <label for="startTime">开始时间 (秒):</label>
              <input
                type="number"
                id="startTime"
                min="0"
                step="0.1"
                value="0"
                placeholder="0"
              />
            </div>
            <div class="control-group">
              <label for="endTime">结束时间 (秒):</label>
              <input
                type="number"
                id="endTime"
                min="0"
                step="0.1"
                placeholder="自动匹配视频时长"
              />
            </div>
          </div>
          <div class="trim-note">💡 留空结束时间将自动匹配视频时长</div>
        </div>
      </div>

      <!-- 处理按钮 -->
      <button class="btn-primary" id="processBtn" disabled>
        🚀 开始替换音频
      </button>

      <!-- 日志下载区域 -->
      <div class="log-section" style="text-align: center; margin: 20px 0">
        <button type="button" id="downloadLogBtn" class="btn-primary">
          📋 下载处理日志
        </button>
      </div>

      <!-- 进度条 -->
      <div class="progress-container" id="progressContainer">
        <div class="progress-bar">
          <div class="progress-fill" id="progressFill"></div>
        </div>
        <div class="progress-text" id="progressText">准备中...</div>
      </div>

      <!-- 状态消息 -->
      <div class="status-message" id="statusMessage"></div>
      <div class="error-message" id="errorMessage"></div>

      <!-- 结果区域 -->
      <div class="result-section" id="resultSection">
        <h3>✅ 处理完成</h3>
        <video class="result-video" id="resultVideo" controls></video>
        <div>
          <button class="download-btn" id="downloadBtn">📥 下载视频</button>
          <button class="btn-primary" id="resetBtn">🔄 重新开始</button>
        </div>
      </div>

      <!-- 使用说明 -->
      <div class="instructions">
        <h3>📋 使用说明</h3>
        <ol>
          <li>分别上传需要处理的视频文件和音频文件</li>
          <li>
            选择处理模式：
            <ul>
              <li>
                <strong>以视频时长为主：</strong
                >音频将被截取或循环以匹配视频时长，可设置音频的开始和结束时间
              </li>
              <li>
                <strong>以音频时长为主：</strong>视频将循环播放直到音频结束
              </li>
            </ul>
          </li>
          <li>
            如选择"以视频时长为主"模式，可设置音频截取的开始和结束时间（可选）
          </li>
          <li>确认设置无误后，点击"开始替换音频"按钮</li>
          <li>等待处理完成，可以实时查看进度</li>
          <li>处理完成后可以预览结果并下载</li>
        </ol>
        <p>
          💡
          提示：所有处理都在您的浏览器中完成，文件不会上传到服务器，保证隐私安全。
          两种模式可以满足不同的使用场景，选择最适合您需求的模式。
        </p>
      </div>

      <footer>
        <p>🔒 本工具完全在浏览器本地运行，保护您的隐私安全</p>
        <p>⚡ 基于 FFmpeg.wasm 技术，无需安装任何软件</p>
      </footer>
    </div>

    <script>
      // 全局变量
      let ffmpeg = null
      let videoFile = null
      let audioFile = null
      let subtitleFile = null
      let subtitleContent = ''
      let processingLogs = [] // 存储处理日志
      let isProcessing = false

      // DOM 元素
      const videoUpload = document.getElementById('videoUpload')
      const audioUpload = document.getElementById('audioUpload')
      const videoInput = document.getElementById('videoInput')
      const audioInput = document.getElementById('audioInput')
      const videoPreview = document.getElementById('videoPreview')
      const audioPreview = document.getElementById('audioPreview')
      const videoElement = document.getElementById('videoElement')
      const audioElement = document.getElementById('audioElement')
      const videoInfo = document.getElementById('videoInfo')
      const audioInfo = document.getElementById('audioInfo')
      const processBtn = document.getElementById('processBtn')
      const downloadLogBtn = document.getElementById('downloadLogBtn')
      const progressContainer = document.getElementById('progressContainer')
      const progressFill = document.getElementById('progressFill')
      const progressText = document.getElementById('progressText')
      const statusMessage = document.getElementById('statusMessage')
      const errorMessage = document.getElementById('errorMessage')
      const resultSection = document.getElementById('resultSection')
      const resultVideo = document.getElementById('resultVideo')
      const downloadBtn = document.getElementById('downloadBtn')
      const resetBtn = document.getElementById('resetBtn')

      // 初始化 FFmpeg
      async function initFFmpeg() {
        if (!ffmpeg) {
          ffmpeg = FFmpeg.createFFmpeg({
            log: true,
          })

          ffmpeg.setLogger(({ type, message }) => {
            // 收集日志
            const timestamp = new Date().toLocaleTimeString()
            const logEntry = `[${timestamp}] [${type}] ${message}`
            processingLogs.push(logEntry)
            console.log(logEntry)

            const timeMatch = message.match(/time=(\d{2}:\d{2}:\d{2}\.\d{2})/)
            if (timeMatch) {
              const timeStr = timeMatch[1]
              updateProgress(50, `处理中... time=${timeStr} (参考时间)`)
            }
          })

          ffmpeg.setProgress(({ ratio }) => {
            const percent = Math.round(ratio * 100)
            // 只更新进度条，不更新文字
            progressFill.style.width = `${percent}%`
          })
        }

        if (!ffmpeg.isLoaded()) {
          showStatus('正在加载 FFmpeg...')
          await ffmpeg.load()
          showStatus('FFmpeg 加载完成')
        }
      }

      // 文件上传处理
      function setupFileUpload(
        uploadArea,
        input,
        previewArea,
        element,
        infoArea,
        fileType
      ) {
        // 点击上传
        uploadArea.addEventListener('click', (e) => {
          // 防止点击input元素时重复触发
          if (e.target === input) return
          e.preventDefault()
          input.click()
        })

        // 拖拽上传
        uploadArea.addEventListener('dragover', (e) => {
          e.preventDefault()
          uploadArea.classList.add('dragover')
        })

        uploadArea.addEventListener('dragleave', () => {
          uploadArea.classList.remove('dragover')
        })

        uploadArea.addEventListener('drop', (e) => {
          e.preventDefault()
          uploadArea.classList.remove('dragover')
          const files = e.dataTransfer.files
          if (files.length > 0) {
            handleFileSelect(files[0], fileType, previewArea, element, infoArea)
          }
        })

        // 文件选择
        input.addEventListener('change', (e) => {
          if (e.target.files.length > 0) {
            handleFileSelect(
              e.target.files[0],
              fileType,
              previewArea,
              element,
              infoArea
            )
            // 清空input的value，确保下次选择相同文件也能触发change事件
            e.target.value = ''
          }
        })
      }

      // 获取视频分辨率
      async function getVideoResolution(videoFile) {
        return new Promise((resolve, reject) => {
          const tempVideo = document.createElement('video')
          tempVideo.src = URL.createObjectURL(videoFile)

          tempVideo.onloadedmetadata = () => {
            const resolution = {
              width: tempVideo.videoWidth || 1920,
              height: tempVideo.videoHeight || 1080,
            }
            URL.revokeObjectURL(tempVideo.src)
            resolve(resolution)
          }

          tempVideo.onerror = () => {
            URL.revokeObjectURL(tempVideo.src)
            // 如果获取失败，返回默认分辨率
            resolve({ width: 1920, height: 1080 })
          }

          // 设置超时，防止无限等待
          setTimeout(() => {
            URL.revokeObjectURL(tempVideo.src)
            resolve({ width: 1920, height: 1080 })
          }, 5000)
        })
      }

      // 字幕文件处理函数
      async function processSubtitleFile(file, videoFile = null) {
        const text = await file.text()
        const extension = file.name.split('.').pop().toLowerCase()

        if (extension === 'lrc') {
          // LRC转SRT
          return convertLrcToSrt(text)
        } else if (extension === 'ass') {
          // ASS规范化处理
          let videoWidth = 1920
          let videoHeight = 1080
          let targetFontSize = 36

          // 如果提供了视频文件，获取其分辨率
          if (videoFile) {
            try {
              const resolution = await getVideoResolution(videoFile)
              videoWidth = resolution.width
              videoHeight = resolution.height
            } catch (error) {
              console.warn('获取视频分辨率失败，使用默认值:', error)
            }
          }

          // 获取字体大小设置
          const fontSizeMode =
            document.getElementById('fontSizeMode')?.value || 'auto'
          if (fontSizeMode === 'custom') {
            targetFontSize = parseInt(
              document.getElementById('customFontSize')?.value || 36
            )
          }

          return normalizeAssSubtitle(
            text,
            videoWidth,
            videoHeight,
            targetFontSize
          )
        } else {
          // SRT直接返回
          return text
        }
      }

      // LRC转SRT函数
      function convertLrcToSrt(lrcContent) {
        const lines = lrcContent.split('\n')
        const subtitles = []
        let index = 1

        for (let i = 0; i < lines.length; i++) {
          const line = lines[i].trim()
          const match = line.match(/\[(\d{2}):(\d{2})\.(\d{2})\](.*)/)

          if (match) {
            const minutes = parseInt(match[1])
            const seconds = parseInt(match[2])
            const centiseconds = parseInt(match[3])
            const text = match[4].trim()

            if (text) {
              const startTime = minutes * 60 + seconds + centiseconds / 100
              const endTime = startTime + 3 // 默认显示3秒

              const startSrt = formatSrtTime(startTime)
              const endSrt = formatSrtTime(endTime)

              subtitles.push(`${index}\n${startSrt} --> ${endSrt}\n${text}\n`)
              index++
            }
          }
        }

        return subtitles.join('\n')
      }

      // SRT时间格式化
      function formatSrtTime(seconds) {
        const hours = Math.floor(seconds / 3600)
        const minutes = Math.floor((seconds % 3600) / 60)
        const secs = Math.floor(seconds % 60)
        const ms = Math.floor((seconds % 1) * 1000)

        return `${hours.toString().padStart(2, '0')}:${minutes
          .toString()
          .padStart(2, '0')}:${secs.toString().padStart(2, '0')},${ms
          .toString()
          .padStart(3, '0')}`
      }

      // ASS字幕规范化
      function normalizeAssSubtitle(
        assContent,
        videoWidth = 1920,
        videoHeight = 1080,
        targetFontSize = 36
      ) {
        // 确保ASS文件有正确的格式头
        if (!assContent.includes('[Script Info]')) {
          const header = `[Script Info]\nTitle: Subtitle\nScriptType: v4.00+\n\n[V4+ Styles]\nFormat: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding\nStyle: Default,Source Han Sans CN,${targetFontSize},&H00FFFFFF,&H000000FF,&H00000000,&H80000000,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1\n\n[Events]\nFormat: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text\n`
          return header + assContent
        }

        // 使用完整的规范化逻辑
        return normalizeAss(assContent, videoWidth, videoHeight, targetFontSize)
      }

      // 完整的ASS字幕规范化函数（参考video-subtitle-merge.html）
      function normalizeAss(
        assContent,
        videoWidth,
        videoHeight,
        targetFontSize
      ) {
        // 计算降级后的分辨率
        let downgradedWidth = videoWidth
        let downgradedHeight = videoHeight

        // 根据原始分辨率降级到下一级标准分辨率
        if (videoWidth >= 3840 && videoHeight >= 2160) {
          // 4K (3840x2160) -> 2K (2560x1440)
          downgradedWidth = 2560
          downgradedHeight = 1440
        } else if (videoWidth >= 2560 && videoHeight >= 1440) {
          // 2K (2560x1440) -> 1080P (1920x1080)
          downgradedWidth = 1920
          downgradedHeight = 1080
        } else if (videoWidth >= 1920 && videoHeight >= 1080) {
          // 1080P (1920x1080) -> 1440P (1440x810)
          downgradedWidth = 1440
          downgradedHeight = 810
        } else if (videoWidth >= 1440 && videoHeight >= 810) {
          // 1440P (1440x810) -> 720P (1280x720)
          downgradedWidth = 1280
          downgradedHeight = 720
        } else if (videoWidth >= 1280 && videoHeight >= 720) {
          // 720P (1280x720) -> 480P (854x480)
          downgradedWidth = 854
          downgradedHeight = 480
        } else if (videoWidth >= 854 && videoHeight >= 480) {
          // 480P (854x480) -> 360P (640x360)
          downgradedWidth = 640
          downgradedHeight = 360
        } else {
          // 更小分辨率保持不变
          downgradedWidth = videoWidth
          downgradedHeight = videoHeight
        }

        const lines = assContent.split('\n')
        let result = []
        let inScriptInfo = false
        let inV4Styles = false
        let inEvents = false

        for (let line of lines) {
          const trimmedLine = line.trim()

          // 处理[Script Info]部分
          if (trimmedLine === '[Script Info]') {
            inScriptInfo = true
            inV4Styles = false
            inEvents = false
            result.push(line)
            continue
          }

          // 处理[V4+ Styles]部分
          if (trimmedLine === '[V4+ Styles]') {
            inScriptInfo = false
            inV4Styles = true
            inEvents = false
            result.push(line)
            continue
          }

          // 处理[Events]部分
          if (trimmedLine === '[Events]') {
            inScriptInfo = false
            inV4Styles = false
            inEvents = true
            result.push(line)
            continue
          }

          // 处理其他section
          if (trimmedLine.startsWith('[') && trimmedLine.endsWith(']')) {
            inScriptInfo = false
            inV4Styles = false
            inEvents = false
            result.push(line)
            continue
          }

          // 在Script Info中重写PlayResX和PlayResY为降级后的分辨率
          if (inScriptInfo) {
            if (trimmedLine.startsWith('PlayResX:')) {
              result.push(`PlayResX: ${downgradedWidth}`)
              continue
            }
            if (trimmedLine.startsWith('PlayResY:')) {
              result.push(`PlayResY: ${downgradedHeight}`)
              continue
            }
            result.push(line)
            continue
          }

          // 在V4+ Styles中统一字体大小和样式
          if (inV4Styles && trimmedLine.startsWith('Style:')) {
            const parts = line.split(',')
            if (parts.length >= 23) {
              // 统一字体大小和字体名称
              parts[2] = targetFontSize.toString() // FontSize
              parts[1] = 'Source Han Sans CN' // FontName
              parts[3] = '100' // ScaleX
              parts[4] = '100' // ScaleY
              result.push(parts.join(','))
              continue
            }
          }

          // 在Events中清理内联字体大小标签并添加默认渐入渐出
          if (
            inEvents &&
            (trimmedLine.startsWith('Dialogue:') ||
              trimmedLine.startsWith('Comment:'))
          ) {
            // 清理内联的\fs、\fscx、\fscy、\fn、\r标签，但保留\move、\fad等动态效果
            let processedLine = line

            // 清理字体大小相关的内联标签
            processedLine = processedLine.replace(/\\fs\d+/g, '') // 清理\fs<size>
            processedLine = processedLine.replace(/\\fscx\d+/g, '') // 清理\fscx<scale>
            processedLine = processedLine.replace(/\\fscy\d+/g, '') // 清理\fscy<scale>
            processedLine = processedLine.replace(/\\fn[^\\}]+/g, '') // 清理\fn<fontname>
            processedLine = processedLine.replace(/\\r[^\\}]+/g, '') // 清理\r<style>

            // 清理可能的多余空格和逗号
            processedLine = processedLine.replace(/\{,+\}/g, '{}') // 清理空的大括号
            processedLine = processedLine.replace(/\{[^}]*\}/g, (match) => {
              // 如果大括号内只剩空格，清理掉
              const content = match.slice(1, -1).trim()
              return content ? match : ''
            })

            // 为每行添加默认的渐入渐出效果（500ms淡入，500ms淡出）
            processedLine = processedLine.replace(
              /\{([^}]*)\}/g,
              (match, content) => {
                // 如果已有fad效果，不重复添加
                if (content.includes('\\fad')) {
                  return match
                }
                // 如果已有其他效果，在现有效果后添加fad
                if (content.trim()) {
                  return `{${content}\\fad(500,500)}`
                }
                // 如果没有效果，直接添加fad
                return '{\\fad(500,500)}'
              }
            )

            // 如果没有大括号，添加包含fad的大括号
            if (!processedLine.includes('{')) {
              const lastCommaIndex = processedLine.lastIndexOf(',')
              if (lastCommaIndex !== -1) {
                const beforeText = processedLine.substring(
                  0,
                  lastCommaIndex + 1
                )
                const afterText = processedLine.substring(lastCommaIndex + 1)
                processedLine = `${beforeText}{\\fad(500,500)}${afterText}`
              }
            }

            result.push(processedLine)
            continue
          }

          // 其他行保持不变
          result.push(line)
        }

        return result.join('\n')
      }

      // 处理文件选择
      async function handleFileSelect(
        file,
        fileType,
        previewArea,
        element,
        infoArea
      ) {
        // 存储文件
        if (fileType === 'video') {
          videoFile = file
          const url = URL.createObjectURL(file)
          element.src = url
          previewArea.style.display = 'block'
        } else if (fileType === 'audio') {
          audioFile = file
          const url = URL.createObjectURL(file)
          element.src = url
          previewArea.style.display = 'block'
        } else if (fileType === 'subtitle') {
          subtitleFile = file
          previewArea.style.display = 'block'

          // 处理字幕文件内容
          try {
            subtitleContent = await processSubtitleFile(file, videoFile)
            showStatus('字幕文件处理成功')

            // 显示字幕设置
            const subtitleSettings = document.getElementById('subtitleSettings')
            if (subtitleSettings) {
              subtitleSettings.style.display = 'block'
            }
          } catch (error) {
            showStatus('字幕文件处理失败: ' + error.message, true)
            subtitleFile = null
            subtitleContent = null
          }
        }

        // 显示文件信息
        if (infoArea) {
          const fileSize = (file.size / (1024 * 1024)).toFixed(2)
          infoArea.innerHTML = `
            <strong>文件名:</strong> ${file.name}<br>
            <strong>大小:</strong> ${fileSize} MB<br>
            <strong>类型:</strong> ${file.type}
          `
        }

        // 检查是否可以开始处理
        checkCanProcess()

        const fileTypeText =
          fileType === 'video' ? '视频' : fileType === 'audio' ? '音频' : '字幕'
        showStatus(`${fileTypeText}文件上传成功`)
      }

      // 检查是否可以开始处理
      function checkCanProcess() {
        processBtn.disabled = !(videoFile && audioFile) || isProcessing
      }

      // 显示状态消息
      function showStatus(message, isError = false) {
        if (isError) {
          errorMessage.textContent = message
          statusMessage.textContent = ''
        } else {
          statusMessage.textContent = message
          errorMessage.textContent = ''
        }
      }

      // 显示错误消息
      function showError(message) {
        errorMessage.textContent = message
        setTimeout(() => {
          errorMessage.textContent = ''
        }, 5000)
      }

      // 更新进度
      function updateProgress(percent, text) {
        progressFill.style.width = `${percent}%`
        progressText.textContent = text
      }

      // 显示进度条
      function showProgress() {
        progressContainer.style.display = 'block'
        statusMessage.classList.add('processing')
      }

      // 隐藏进度条
      function hideProgress() {
        progressContainer.style.display = 'none'
        statusMessage.classList.remove('processing')
      }

      // 处理视频音频替换
      async function processVideoAudio() {
        if (!videoFile || !audioFile || isProcessing) return

        // 清空之前的日志
        processingLogs = []
        processingLogs.push(
          `[${new Date().toLocaleTimeString()}] [info] 开始处理视频: ${
            videoFile.name
          }`
        )
        processingLogs.push(
          `[${new Date().toLocaleTimeString()}] [info] 音频文件: ${
            audioFile.name
          }`
        )
        if (subtitleFile) {
          processingLogs.push(
            `[${new Date().toLocaleTimeString()}] [info] 字幕文件: ${
              subtitleFile.name
            }`
          )
        }

        isProcessing = true
        processBtn.disabled = true
        showProgress()

        try {
          // 初始化 FFmpeg
          await initFFmpeg()

          // 生成文件名
          const videoName = 'input_video.' + videoFile.name.split('.').pop()
          const audioName = 'input_audio.' + audioFile.name.split('.').pop()
          const outputName = 'output_video.mp4'
          let subtitleName = null

          showStatus('正在读取文件...')

          // 将文件写入 FFmpeg 文件系统
          const videoData = new Uint8Array(await videoFile.arrayBuffer())
          const audioData = new Uint8Array(await audioFile.arrayBuffer())

          ffmpeg.FS('writeFile', videoName, videoData)
          ffmpeg.FS('writeFile', audioName, audioData)

          // 处理字幕文件
          if (subtitleFile && subtitleContent) {
            const extension = subtitleFile.name.split('.').pop().toLowerCase()
            subtitleName = extension === 'ass' ? 'subtitle.ass' : 'subtitle.srt'

            const subtitleData = new TextEncoder().encode(subtitleContent)
            ffmpeg.FS('writeFile', subtitleName, subtitleData)
          }

          // 写入字体文件到FFmpeg文件系统（解决字体缺失问题）
          if (subtitleName) {
            try {
              const fontResponse = await fetch('./SourceHanSansCN-Regular.ttf')
              const fontData = new Uint8Array(await fontResponse.arrayBuffer())
              ffmpeg.FS('writeFile', 'SourceHanSansCN-Regular.ttf', fontData)
              processingLogs.push(
                `[${new Date().toLocaleTimeString()}] [info] 字体文件加载成功: SourceHanSansCN-Regular.ttf`
              )
            } catch (e) {
              console.warn('字体文件加载失败，使用默认字体:', e)
              processingLogs.push(
                `[${new Date().toLocaleTimeString()}] [warning] 字体文件加载失败，使用默认字体: ${
                  e.message
                }`
              )
            }
          }

          // 获取选择的模式
          const mode = document.querySelector(
            'input[name="mode"]:checked'
          ).value

          showStatus('开始处理视频...')

          let ffmpegArgs = []

          if (mode === 'video') {
            // 以视频时长为主模式
            const startTime =
              parseFloat(document.getElementById('startTime').value) || 0
            const endTime = parseFloat(document.getElementById('endTime').value)

            ffmpegArgs = ['-i', videoName]

            // 添加音频截取参数
            if (startTime > 0) {
              ffmpegArgs.push('-ss', startTime.toString())
            }

            ffmpegArgs.push('-i', audioName)

            if (endTime && endTime > startTime) {
              ffmpegArgs.push('-t', (endTime - startTime).toString())
            }

            // 构建视频滤镜
            let videoFilter = '[0:v]'

            // 如果有字幕，添加字幕滤镜
            if (subtitleName) {
              const subtitleStyle =
                document.getElementById('subtitleStyle')?.value || 'default'
              const fontSizeMode =
                document.getElementById('fontSizeMode')?.value || 'auto'
              const customFontSize =
                document.getElementById('customFontSize')?.value || 36

              // 根据字体大小模式确定字体大小
              let fontSize = 36 // 默认字体大小
              if (fontSizeMode === 'custom') {
                fontSize = customFontSize
              }

              if (subtitleName.endsWith('.ass')) {
                // ASS字幕处理 - 使用subtitles滤镜
                let forceStyle = `FontName=Source Han Sans CN,FontSize=${fontSize}`

                if (subtitleStyle === 'default') {
                  forceStyle +=
                    ',PrimaryColour=0xffffff,OutlineColour=0x000000,BackColour=0x80000000,Outline=2,Shadow=1,MarginV=50'
                } else if (subtitleStyle === 'bold') {
                  forceStyle +=
                    ',PrimaryColour=0xffffff,OutlineColour=0x000000,BackColour=0x80000000,Bold=1,Outline=2,Shadow=1,MarginV=50'
                } else if (subtitleStyle === 'shadow') {
                  forceStyle +=
                    ',PrimaryColour=0xffffff,OutlineColour=0x000000,BackColour=0x80000000,Outline=3,Shadow=2,MarginV=50'
                }

                videoFilter += `[v];[v]subtitles=${subtitleName}:fontsdir=.:force_style='${forceStyle}'[v]`
              } else {
                // SRT/LRC字幕处理
                let forceStyle = `FontName=Source Han Sans CN,FontSize=${fontSize}`

                if (subtitleStyle === 'bold') {
                  forceStyle += ',Bold=1'
                } else if (subtitleStyle === 'shadow') {
                  forceStyle += ',Outline=2,Shadow=2'
                }

                videoFilter += `[v];[v]subtitles=${subtitleName}:fontsdir=.:force_style='${forceStyle}'[v]`
              }

              ffmpegArgs.push(
                '-vf',
                videoFilter.replace('[0:v][v];[v]', '[0:v]').replace('[v]', '')
              )
              ffmpegArgs.push('-c:v', 'libx264') // 需要重新编码以应用字幕
            } else {
              ffmpegArgs.push('-c:v', 'copy') // 复制视频流，不重新编码
            }

            ffmpegArgs.push(
              '-c:a',
              'aac', // 音频编码为 AAC
              '-map',
              '0:v:0', // 映射视频流
              '-map',
              '1:a:0', // 映射音频流
              '-shortest', // 以最短的流为准
              '-y',
              outputName
            )
          } else {
            // 以音频时长为主模式
            ffmpegArgs = [
              '-stream_loop',
              '-1', // 无限循环视频
              '-i',
              videoName,
              '-i',
              audioName,
            ]

            // 如果有字幕，添加字幕滤镜
            if (subtitleName) {
              const subtitleStyle =
                document.getElementById('subtitleStyle')?.value || 'default'
              const fontSizeMode =
                document.getElementById('fontSizeMode')?.value || 'auto'
              const customFontSize =
                document.getElementById('customFontSize')?.value || 36

              // 根据字体大小模式确定字体大小
              let fontSize = 36 // 默认字体大小
              if (fontSizeMode === 'custom') {
                fontSize = customFontSize
              }

              if (subtitleName.endsWith('.ass')) {
                // ASS字幕处理 - 使用subtitles滤镜
                let forceStyle = `FontName=Source Han Sans CN,FontSize=${fontSize}`

                if (subtitleStyle === 'default') {
                  forceStyle +=
                    ',PrimaryColour=0xffffff,OutlineColour=0x000000,BackColour=0x80000000,Outline=2,Shadow=1,MarginV=50'
                } else if (subtitleStyle === 'bold') {
                  forceStyle +=
                    ',PrimaryColour=0xffffff,OutlineColour=0x000000,BackColour=0x80000000,Bold=1,Outline=2,Shadow=1,MarginV=50'
                } else if (subtitleStyle === 'shadow') {
                  forceStyle +=
                    ',PrimaryColour=0xffffff,OutlineColour=0x000000,BackColour=0x80000000,Outline=3,Shadow=2,MarginV=50'
                }

                ffmpegArgs.push(
                  '-vf',
                  `subtitles=${subtitleName}:fontsdir=.:force_style='${forceStyle}'`
                )
              } else {
                // SRT/LRC字幕处理
                let forceStyle = `FontName=Source Han Sans CN,FontSize=${fontSize}`

                if (subtitleStyle === 'bold') {
                  forceStyle += ',Bold=1'
                } else if (subtitleStyle === 'shadow') {
                  forceStyle += ',Outline=2,Shadow=2'
                }

                ffmpegArgs.push(
                  '-vf',
                  `subtitles=${subtitleName}:fontsdir=.:force_style='${forceStyle}'`
                )
              }
            }

            ffmpegArgs.push(
              '-c:v',
              'libx264', // 重新编码视频以支持循环
              '-c:a',
              'aac', // 音频编码为 AAC
              '-map',
              '0:v:0', // 映射视频流
              '-map',
              '1:a:0', // 映射音频流
              '-shortest', // 以音频时长为准
              '-y',
              outputName
            )
          }

          // 执行 FFmpeg 命令
          await ffmpeg.run(...ffmpegArgs)

          showStatus('正在生成结果...')

          // 读取输出文件
          const outputData = ffmpeg.FS('readFile', outputName)
          const outputBlob = new Blob([outputData.buffer], {
            type: 'video/mp4',
          })
          const outputUrl = URL.createObjectURL(outputBlob)

          // 显示结果
          resultVideo.src = outputUrl
          resultSection.style.display = 'block'

          // 设置下载
          downloadBtn.onclick = () => {
            const a = document.createElement('a')
            a.href = outputUrl
            const modeText = mode === 'video' ? 'video_based' : 'audio_based'
            a.download = `${modeText}_${videoFile.name.split('.')[0]}.mp4`
            a.click()
          }

          showStatus('处理完成！')
          hideProgress()

          // 清理文件系统
          ffmpeg.FS('unlink', videoName)
          ffmpeg.FS('unlink', audioName)
          if (subtitleName) {
            ffmpeg.FS('unlink', subtitleName)
          }
          ffmpeg.FS('unlink', outputName)
        } catch (error) {
          console.error('处理失败:', error)
          showStatus('处理失败: ' + error.message, true)
          hideProgress()
        } finally {
          isProcessing = false
          checkCanProcess()
        }
      }

      // 重置所有状态
      function resetAll() {
        videoFile = null
        audioFile = null
        subtitleFile = null
        subtitleContent = null
        processingLogs = [] // 清空日志
        isProcessing = false

        videoInput.value = ''
        audioInput.value = ''
        const subtitleInput = document.getElementById('subtitleInput')
        if (subtitleInput) subtitleInput.value = ''

        videoPreview.style.display = 'none'
        audioPreview.style.display = 'none'
        const subtitlePreview = document.getElementById('subtitlePreview')
        if (subtitlePreview) subtitlePreview.style.display = 'none'

        const subtitleSettings = document.getElementById('subtitleSettings')
        if (subtitleSettings) subtitleSettings.style.display = 'none'

        resultSection.style.display = 'none'

        hideProgress()
        showStatus('')
        checkCanProcess()
      }

      // 初始化事件监听器
      function initEventListeners() {
        setupFileUpload(
          videoUpload,
          videoInput,
          videoPreview,
          videoElement,
          videoInfo,
          'video'
        )
        setupFileUpload(
          audioUpload,
          audioInput,
          audioPreview,
          audioElement,
          audioInfo,
          'audio'
        )

        // 字幕文件上传
        const subtitleUpload = document.getElementById('subtitleUpload')
        const subtitleInput = document.getElementById('subtitleInput')
        const subtitlePreview = document.getElementById('subtitlePreview')
        const subtitleInfo = document.getElementById('subtitleInfo')

        if (
          subtitleUpload &&
          subtitleInput &&
          subtitlePreview &&
          subtitleInfo
        ) {
          setupFileUpload(
            subtitleUpload,
            subtitleInput,
            subtitlePreview,
            null,
            subtitleInfo,
            'subtitle'
          )
        }

        processBtn.addEventListener('click', processVideoAudio)
        resetBtn.addEventListener('click', resetAll)

        // 下载日志按钮事件监听器
        downloadLogBtn.addEventListener('click', () => {
          if (processingLogs.length === 0) {
            showError('❌ 暂无处理日志')
            return
          }

          const logContent = processingLogs.join('\n')
          const blob = new Blob([logContent], {
            type: 'text/plain;charset=utf-8',
          })
          const url = URL.createObjectURL(blob)

          const a = document.createElement('a')
          a.href = url
          a.download = `video-audio-replace-log-${new Date()
            .toISOString()
            .slice(0, 19)
            .replace(/:/g, '-')}.txt`
          document.body.appendChild(a)
          a.click()
          document.body.removeChild(a)
          URL.revokeObjectURL(url)
        })

        // 模式切换事件监听器
        const modeRadios = document.querySelectorAll('input[name="mode"]')
        const audioTrimSettings = document.getElementById('audioTrimSettings')

        modeRadios.forEach((radio) => {
          radio.addEventListener('change', (e) => {
            if (e.target.value === 'video') {
              audioTrimSettings.classList.remove('hidden')
            } else {
              audioTrimSettings.classList.add('hidden')
            }
          })
        })

        // 字体大小模式切换事件监听器
        const fontSizeMode = document.getElementById('fontSizeMode')
        const customFontSizeGroup = document.getElementById(
          'customFontSizeGroup'
        )

        if (fontSizeMode && customFontSizeGroup) {
          fontSizeMode.addEventListener('change', (e) => {
            if (e.target.value === 'custom') {
              customFontSizeGroup.style.display = 'block'
            } else {
              customFontSizeGroup.style.display = 'none'
            }
          })
        }

        // 自定义字体大小滑块事件监听器
        const customFontSize = document.getElementById('customFontSize')
        const fontSizeValue = document.getElementById('fontSizeValue')

        if (customFontSize && fontSizeValue) {
          customFontSize.addEventListener('input', (e) => {
            fontSizeValue.textContent = e.target.value
          })
        }
      }

      // 页面加载完成后初始化
      document.addEventListener('DOMContentLoaded', () => {
        initEventListeners()
        showStatus('请上传视频和音频文件开始处理')
      })
    </script>
  </body>
</html>
